package com.shareppy.utils;

import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public class Holidays {

	private static Map<Integer, Set<Holiday>> computedHolidays = new HashMap<Integer, Set<Holiday>>();

	public static List<Holiday> holidaysDef = Arrays.asList(new Holiday[] {//
			new Holiday(0, Calendar.JANUARY, 1, true), //
					new Holiday(0, Calendar.JANUARY, 6), //
					new Holiday(0, Calendar.MARCH, 19), //
					new Holiday(0, Calendar.MAY, 1, true), //
					new Holiday(0, Calendar.JUNE, 29), //
					new Holiday(0, Calendar.JULY, 20, true), //
					new Holiday(0, Calendar.AUGUST, 7, true), //
					new Holiday(0, Calendar.AUGUST, 15), //
					new Holiday(0, Calendar.OCTOBER, 12), //
					new Holiday(0, Calendar.NOVEMBER, 1), //
					new Holiday(0, Calendar.NOVEMBER, 11), //
					new Holiday(0, Calendar.DECEMBER, 8, true), //
					new Holiday(0, Calendar.DECEMBER, 25, true) });

	private static Set<Holiday> compute(int year) {
		Set<Holiday> holidays = new TreeSet<Holiday>();
		holidays.addAll(computeHolyWeek(year));
		for (Holiday holiday : holidaysDef) {
			Holiday nextMonday = new Holiday(year, holiday.month, holiday.day,
					holiday.fixed, holiday.description).toNextMonday();
			holidays.add(nextMonday);
		}

		return holidays;
	}

	public static Set<Holiday> computeHolyWeek(int year) {
		Holiday easterSunday = computeEasterSunday(year);
		Set<Holiday> holydays = new TreeSet<Holiday>();
		holydays.add(easterSunday);
		Calendar calendar = easterSunday.toCalendar();

		holydays.add(new Holiday(year, calendar.get(Calendar.MONTH), calendar
				.get(Calendar.DAY_OF_MONTH) - 2, true));
		holydays.add(new Holiday(year, calendar.get(Calendar.MONTH), calendar
				.get(Calendar.DAY_OF_MONTH) - 3, true));
		holydays.add(new Holiday(year, calendar.get(Calendar.MONTH), calendar
				.get(Calendar.DAY_OF_MONTH) - 7, true));
		holydays.add(new Holiday(year, calendar.get(Calendar.MONTH), calendar
				.get(Calendar.DAY_OF_MONTH) + 43, true));
		holydays.add(new Holiday(year, calendar.get(Calendar.MONTH), calendar
				.get(Calendar.DAY_OF_MONTH) + 64, true));
		holydays.add(new Holiday(year, calendar.get(Calendar.MONTH), calendar
				.get(Calendar.DAY_OF_MONTH) + 71, true));

		return holydays;
	}

	private static Holiday computeEasterSunday(int year) {
		int A = year % 19;
		int B = year / 100;
		int C = year % 100;
		int D = B / 4;
		int E = B % 4;
		int F = (B + 8) / 25;
		int G = (B - F + 1) / 3;
		int H = (19 * A + B - D - G + 15) % 30;
		int I = C / 4;
		int K = C % 4;
		int L = (32 + 2 * E + 2 * I - H - K) % 7;
		int M = (A + 11 * H + 22 * L) / 451;
		int N = H + L - 7 * M + 114;
		int month = (N / 31) - 1;
		int day = 1 + (N % 31);
		return new Holiday(year, month, day);
	}

	public static Holiday getNextHoliday(Calendar date, int year) {
		Set<Holiday> compute = compute(year);
		for (Holiday holiday : compute) {
			if (holiday.toCalendar().after(date))
				return holiday;
		}
		return null;
	}

	public static boolean isHoliday(Calendar calendar) {
		Set<Holiday> holidaysForYear = getHolidaysForYear(calendar.get(Calendar.YEAR));
		return holidaysForYear.contains(
				new Holiday(calendar));
	}

	public static Set<Holiday> getHolidaysForYear(Integer _year) {
		Integer year = Integer.valueOf(_year);
		Set<Holiday> set = computedHolidays.get(year);
		if (set == null) {
			computedHolidays.put(year, compute(year));
			set = computedHolidays.get(year);
		}
		return set;
	}
}